#include "stdafx.h"
#include "IndexMap.h"
#include "IIndexableFile.h"
#include "IndexableFileFactory.h"
#include "DirectoryExplorer.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <map>
#include <ctime>
#include <vector>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/set.hpp>

const std::string EXTENSION = ".txt";
const std::string FILE_NAME_FOLDERS_TO_INDEX = "ONetIndex.txt";
const std::string FILE_NAME_TO_INDEX = "ONetFileNameToIndex_";
const std::string INDEX_TO_WORD = "ONetIndexToWord_";

std::string getTempPath();

bool saveMapStringToInt(const std::string& fileName, const std::map<std::string,int>& serMap);

bool saveMapStringToSetInt(const std::string& fileName, const std::map<std::string, std::set<int>>& serMap);

bool loadMapStringToInt(const std::string& fileName, std::vector<std::pair<std::string,int>>& serMap);

bool loadMapStringToSetInt(const std::string& fileName, std::vector<std::pair<std::string, std::vector<int>>>& serMap);

std::string getFullPath (std::string, int numberOfFile);

std::string getPathToBaseFile();

template <typename T> bool serializeMap(std::string fileName, T& serMap);

IndexMap::IndexMap()
{
	/*this->filenameToIndex=new std::map<std::string,std::set<int>>();
	this->wordToIndex=new std::map<std::string,int>();
	this->indexToWord=new std::map<int,std::string>();*/
}

bool IndexMap::containsWord(std::string& word,std::string& filename)
{
	int indexOfWord = wordToIndex[word];

//	std::set<int>& indexes = filenameToIndex[filename];

	return (filenameToIndex[filename].count(indexOfWord)==1);
}

std::set<std::string>* IndexMap::containsWords(std::set<std::string>& words,std::string& filename)
{
	

	std::set<int>& indexes = filenameToIndex[filename];

	std::set<std::string>* result = new std::set<std::string>();
	
	for(std::set<std::string>::iterator it = words.begin(); it != words.end(); it++)
	{
		std::string str = *it;

		int indexOfWord = wordToIndex[str];

		if(indexes.count(indexOfWord)==1)
		{
			result->insert(str);
		}
	}

	return result;
}

void IndexMap::addWord(std::string& word, std::string& filename, std::set<int>& indexes)
{
	using namespace std;

	map<string, int>::iterator findedIndexOfWord = wordToIndex.find(word);

	if (findedIndexOfWord != wordToIndex.end())
	{
		indexes.insert(findedIndexOfWord->second);
	}
	else
	{
		int index = this->wordToIndex.size();
		this->wordToIndex.insert(pair<string, int>(word, index));
		indexes.insert(index);
	}
}

bool IndexMap::containsIndex(std::string& filename) const
{
	return (filenameToIndex.count(filename)==1);
}

std::set<int>* IndexMap::getIndex(std::string& filename) 
{
	return &filenameToIndex[filename];
}

void IndexMap::save(std::string& pathToDir)
{
	using namespace std;

	string baseFileName = getPathToBaseFile();

	map<string, int> mapOfDirPath;

	ifstream iStream(baseFileName.c_str());
	if (iStream)
	{
		try 
		{
			boost::archive::binary_iarchive ia(iStream);
			ia >> mapOfDirPath;
		}
		catch(boost::archive::archive_exception& ex)
		{
//			std::cout << "archive exception: " << ex.what() <<std::endl;
		}
		iStream.close();
	}

	map<string, int>::iterator mapIterator = mapOfDirPath.find(pathToDir);

	string fileNameIndexToWord;
	string ONetFileNameToIndex;

	if (mapIterator != mapOfDirPath.end())
	{
		int number = mapIterator->second;

		fileNameIndexToWord= getFullPath(INDEX_TO_WORD, number);
		ONetFileNameToIndex= getFullPath(FILE_NAME_TO_INDEX, number);
	}
	else
	{
		int countDir = mapOfDirPath.size();

		fileNameIndexToWord= getFullPath(INDEX_TO_WORD, countDir);
		ONetFileNameToIndex= getFullPath(FILE_NAME_TO_INDEX, countDir);

		mapOfDirPath.insert(pair<string, int>(pathToDir, mapOfDirPath.size()));
	}

	if (!saveMapStringToInt(fileNameIndexToWord, this->wordToIndex))
	{
		return;
	}

	if (!saveMapStringToSetInt(ONetFileNameToIndex, this->filenameToIndex))
	{
		return;
	}

	if (mapIterator == mapOfDirPath.end())
	{
		if (!serializeMap(baseFileName, mapOfDirPath))
		{
			return;
		}
	}
};

IndexesToSearch* IndexMap::load(std::string& pathToDir)
{
	std::string fileNameOfIndexDir = getPathToBaseFile();

	std::cout << "Loading index..." ;

	std::ifstream fOfDirs(fileNameOfIndexDir.c_str(),std::ios::binary);

	if (!fOfDirs)
	{
		std::cout << "failed"<<std::endl ;
		return NULL;
	}

	std::map<std::string,int> mapOfDirectory;

	try 
	{
		boost::archive::binary_iarchive aOfDirs(fOfDirs);
		aOfDirs >> mapOfDirectory;
	}
	catch(boost::archive::archive_exception& ex)
	{
		std::cout << "failed" << std::endl  ;
		fOfDirs.close();
		return NULL;
	}

	fOfDirs.close();

	std::map<std::string,int>::iterator findedDir = mapOfDirectory.find(pathToDir);

	if  (findedDir == mapOfDirectory.end())
	{
		std::cout << "failed" << std::endl  ;
		return NULL;
	}
	else
	{

		int numberOfFile = findedDir->second;

		std::string fileNameToIndex = getFullPath(FILE_NAME_TO_INDEX, numberOfFile);

		std::string indexToWord = getFullPath(INDEX_TO_WORD, numberOfFile);

		IndexesToSearch* indexMap = new IndexesToSearch();

		if (!loadMapStringToInt(indexToWord,indexMap->wordToIndex))
		{
			std::cout << "failed" << std::endl  ;
			delete(indexMap);
			return NULL;
		}

		if (!loadMapStringToSetInt(fileNameToIndex,indexMap->filenameToIndex))
		{
			std::cout << "failed" << std::endl  ;
			delete(indexMap);
			return NULL;
		}

		std::cout << "ok" << std::endl  ;

		return indexMap;

	}
}

void IndexMap::create(std::string& pathToDir)
{
	using namespace std;

	time_t startTime, currentTime;
	double dif;
	const char STATE[] =  {'-','\\','|','/'};
	time(&startTime);

	IndexMap* indexMap = new IndexMap();

	DirectoryExplorer* explorer = new DirectoryExplorer(pathToDir);
	IndexableFileFactory* factory = new IndexableFileFactory();	

	int indexOfState = 0;
	string file;

	while((file = explorer->getNextFile()) != "")
	{		
		if (indexOfState == 4)
		{
			indexOfState = 0;
		}

		cout<< '\r' << "Indexing in progress" << STATE[indexOfState];

		IIndexableFile* iFile = factory->createIndexableFile(file);

		if (iFile != NULL)
		{
			indexOfState++;

			iFile->buildIndex(indexMap);
		}

		time(&currentTime);

		if(currentTime - startTime >= 290) // ten seconds left to save the index
		{
			//cout << "\n" << "Time is over" << endl;

			break;
		}
		
	}

	cout << '\r' << "                                       " << endl;

	std::cout << "Saving index..." ;

	indexMap->save(pathToDir);

	std::cout << "ok" ;

	delete(factory);
	delete(explorer);
	delete(indexMap);
};

std::string getTempPath()
{
	long dwRetVal = 0;
	char lpTempPathBuffer[MAX_PATH];
	dwRetVal = GetTempPath(MAX_PATH,          // length of the buffer
                           lpTempPathBuffer); // buffer for path 
	if (dwRetVal > MAX_PATH || (dwRetVal == 0))
    {
		std::cout << "GetTempPath failed" << std::endl;
    }

	return std::string(lpTempPathBuffer);
}

template <typename T> bool serializeMap(std::string fileName, T& serMap)
{
	using namespace std;
	ofstream oStream(fileName.c_str(), std::ios::binary);
	if (!oStream)
	{
		cout << "Can't open file \"" << fileName << "\" for writing." <<endl;
		return false;
	}
	boost::archive::binary_oarchive oa(oStream);
	oa << serMap;
	oStream.close();
	return true;
}

bool saveMapStringToInt(const std::string& fileName, const std::map<std::string,int>& serMap)
{
	using namespace std;
	
	ofstream oStream(fileName.c_str(), std::ios::out | std::ios::binary);

	if (!oStream)
	{
		cout << "Can't open file \"" << fileName << "\" for writing." <<endl;
		return false;
	}
	
	int s = serMap.size();

	oStream.write(reinterpret_cast<char*>(&s),sizeof(int));

	for(std::map<std::string,int>::const_iterator word = serMap.begin();
		word!= serMap.end(); word++)
	{
		int sizeWord = word->first.size();
		oStream.write(reinterpret_cast<char*>(&sizeWord), sizeof(int));
		oStream.write((&word->first[0]), sizeWord);
		int index = word->second;
		oStream.write(reinterpret_cast<char*>(&index), sizeof(int));
	}

	oStream.close();
	return true;
}

bool saveMapStringToSetInt(const std::string& fileName,const std::map<std::string, std::set<int>>& serMap)
{
	using namespace std;
	
	ofstream oStream(fileName.c_str(), std::ios::out | std::ios::binary);
	if (!oStream)
	{
		cout << "Can't open file \"" << fileName << "\" for writing." <<endl;
		return false;
	}

	int count = serMap.size();

	oStream.write(reinterpret_cast<char*>(&count), sizeof(int));

	for(std::map<std::string,set<int>>::const_iterator word = serMap.begin();
		word!= serMap.end(); word++)
	{
		int wordSize = word->first.size();
		oStream.write(reinterpret_cast<char*>(&wordSize),sizeof(int));
		const set<int>& indexes = (word->second);

		int countIndexes = indexes.size();
		oStream.write(reinterpret_cast<char*>(&countIndexes),sizeof(int));

		oStream.write((&word->first[0]),wordSize);

		for(std::set<int>::const_iterator index = indexes.begin();
				index != indexes.end(); index++)
		{
			int i = *index;
			oStream.write(reinterpret_cast<char*>(&i), sizeof(int));
		}
	}

	oStream.close();
	return true;
}

bool loadMapStringToInt(const std::string& fileName, std::vector<std::pair<std::string,int>>& serMap)
{
	using namespace std;
	
	ifstream iStream(fileName.c_str(), std::ios::in | std::ios::binary);

	if (!iStream)
	{			
		cout << "Can't open file \"" << fileName << "\" for reading." <<endl;
		return false;
	}

	int count ;
	iStream.read(reinterpret_cast<char*>(&count), sizeof(int));
	serMap.reserve(count);
	
	while(count > 0)
	{
		int size;

		iStream.read(reinterpret_cast<char*>(&size), sizeof(int));
		char *buffer = new char[size];
		iStream.read(buffer, size);

		int index;

		iStream.read(reinterpret_cast<char*>(&index), sizeof(int));
		serMap.push_back(std::pair<std::string, int>(std::string(buffer, size), index));
		
		count--;
	}

	iStream.close();
	return true;
}

bool loadMapStringToSetInt(const std::string& fileName, std::vector<std::pair<std::string, std::vector<int>>>& serMap)
{
	using namespace std;

	ifstream iStream(fileName.c_str(), std::ios::in | std::ios::binary);

	if (!iStream)
	{

		cout << "Can't open file \"" << fileName << "\" for reading." <<endl;
		return false;
	}

	int count;

	iStream.read(reinterpret_cast<char*>(&count), sizeof(int));

	serMap.reserve(count);

	int countAddedEl = 0;

	while(countAddedEl < count)
	{
		int size, countIndex; 

		iStream.read(reinterpret_cast<char*>(&size), sizeof(int));
		iStream.read(reinterpret_cast<char*>(&countIndex), sizeof(int));

		std::string indexedFilename; 
		indexedFilename.resize(size);

		serMap.push_back(std::pair<std::string, std::vector<int>>(indexedFilename, std::vector<int>(countIndex)));
		std::pair<std::string, std::vector<int>>& pairInSerMap = serMap[countAddedEl];
		iStream.read(&(pairInSerMap.first[0]), size);
						

		if(countIndex > 0)
		{	
			std::vector<int>& indexes = pairInSerMap.second;
			iStream.read(reinterpret_cast<char*>(&(indexes[0])), sizeof(int) * countIndex);
		}

		countAddedEl++;

	}

	iStream.close();
	return true;
}

std::string getFullPath(std::string filename, int numberOfFile)
{
	std::stringstream ssFileNameToIndex;
	ssFileNameToIndex<< getTempPath() << filename << numberOfFile << EXTENSION;
	return ssFileNameToIndex.str();
}

std::string getPathToBaseFile()
{
	return (getTempPath() + FILE_NAME_FOLDERS_TO_INDEX);
}
